import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacpp.FloatPointer;
import org.bytedeco.javacpp.Pointer;
import org.bytedeco.javacv.*;
import org.bytedeco.opencv.global.opencv_imgcodecs;
import org.bytedeco.opencv.opencv_core.*;
import org.bytedeco.opencv.opencv_face.Facemark;
import org.bytedeco.opencv.opencv_face.FacemarkLBF;
import org.bytedeco.opencv.opencv_imgproc.Subdiv2D;
import org.bytedeco.opencv.opencv_imgproc.Vec6fVector;
import org.bytedeco.opencv.opencv_objdetect.CascadeClassifier;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;

import static org.bytedeco.opencv.global.opencv_core.*;
import static org.bytedeco.opencv.global.opencv_face.drawFacemarks;
import static org.bytedeco.opencv.global.opencv_highgui.*;
import static org.bytedeco.opencv.global.opencv_imgproc.*;

public class Demo {

    private static String root = Demo.class.getResource("").getFile();
    private static File face = new File(root + File.separator + "data/haarcascades/haarcascade_frontalface_default.xml");
    private static File lbfmodel = new File(root + File.separator + "lbfmodel.yaml");
    private static Facemark facemark;
    private static CascadeClassifier classifier;

    public static Point2f change(FloatPointer pointer) {
        Point2f point2f = new Point2f();
        point2f.put(new Point2f(pointer.get(0), pointer.get(1)));
        point2f.put(new Point2f(pointer.get(2), pointer.get(3)));
        point2f.put(new Point2f(pointer.get(4), pointer.get(5)));
        return point2f;
    }

    public static void initCascadeClassifier() {
        classifier = new CascadeClassifier(face.getPath());
    }

    public static void initFaceMark() {
        facemark = FacemarkLBF.create();
        facemark.loadModel(lbfmodel.getPath());
    }

    public static Point2fVectorVector getFaceMark(Mat mat, RectVector face) {
        Point2fVectorVector vector = new Point2fVectorVector();
        facemark.fit(mat, face, vector);
        return vector;
    }

    public static Vec6fVector getTriangleList(Rect face, Point2fVector vector) {
        Subdiv2D subdiv2D = new Subdiv2D(face);
        subdiv2D.insert(vector);
        Vec6fVector dstTriangleList = new Vec6fVector();
        subdiv2D.getTriangleList(dstTriangleList);
        return dstTriangleList;
    }

    public static Pointer changeToPinter(FloatPointer pointer) {
        Pointer p = new Pointer();
        p.put(new Point2f(pointer.get(0), pointer.get(1)));
        p.put(new Point2f(pointer.get(2), pointer.get(3)));
        p.put(new Point2f(pointer.get(4), pointer.get(5)));
        return p;
    }

    public static Range changeToRange(FloatPointer pointer) {
        Range range = new Range();
        range.position(0).put(new Point2f(pointer.get(0), pointer.get(1)));
        range.position(1).put(new Point2f(pointer.get(2), pointer.get(3)));
        range.position(2).put(new Point2f(pointer.get(4), pointer.get(5)));
        return range;
    }

    public static MatVector changeToMatVector(FloatPointer pointer) {
        MatVector p = new MatVector();
        p.position(0).put(new Point2f(pointer.get(0), pointer.get(1)));
        p.position(1).put(new Point2f(pointer.get(2), pointer.get(3)));
        p.position(2).put(new Point2f(pointer.get(4), pointer.get(5)));
        System.out.println(pointer.get(0) + ":" + pointer.get(1));
        return p;
    }

    public static void main(String[] args) throws Exception {
        initCascadeClassifier();
        initFaceMark();
        Mat src = opencv_imgcodecs.imread("E:\\1.jpg");
        //Mat dst = opencv_imgcodecs.imread("E:\\2.jpg");
        Mat dst = opencv_imgcodecs.imread("E:\\2.jfif");
        RectVector srcFaces = new RectVector();
        RectVector dstFaces = new RectVector();
        Mat srcGray = new Mat();
        Mat dstGray = new Mat();
        cvtColor(src, srcGray, COLOR_BGRA2GRAY);
        cvtColor(dst, dstGray, COLOR_BGRA2GRAY);
        equalizeHist(srcGray, srcGray);
        equalizeHist(dstGray, dstGray);
        //人脸识别
        classifier.detectMultiScale(srcGray, srcFaces);
        classifier.detectMultiScale(dstGray, dstFaces);
        rectangle(dst, dstFaces.get(0), new Scalar().green(255), 1, 8, 0);
        //关键点检测
        Point2fVectorVector srcVector = getFaceMark(src, srcFaces);
        Point2fVectorVector dstVector = getFaceMark(dst, dstFaces);
        Point2fVector srcPoint2fVector = srcVector.get(0);
        Point2fVector dstPoint2fVector = dstVector.get(0);
        for (int i = 0; i < srcPoint2fVector.size(); i++) {
            circle(dst, new Point((int) srcPoint2fVector.get(i).x(), (int) srcPoint2fVector.get(i).y()), 2, new Scalar().green(255), -1, 8, 0);
        }

        drawFacemarks(src, srcPoint2fVector, new Scalar().green(255));
        Vec6fVector srcTriangleList = getTriangleList(srcFaces.get(0), srcPoint2fVector);
        //Vec6fVector dstTriangleList = getTriangleList(dstFaces.get(0), dstPoint2fVector);
        /*for (int i = 0; i < dstTriangleList.size(); i++) {
            FloatPointer pointer = dstTriangleList.get(i);
            Point p1 = new Point(cvRound(pointer.get(0)), cvRound(pointer.get(1)));
            Point p2 = new Point(cvRound(pointer.get(2)), cvRound(pointer.get(3)));
            Point p3 = new Point(cvRound(pointer.get(4)), cvRound(pointer.get(5)));
            line(dst, p1, p2, new Scalar().green(255), 1, 8, 0);
            line(dst, p2, p3, new Scalar().green(255), 1, 8, 0);
            line(dst, p3, p1, new Scalar().green(255), 1, 8, 0);
        }*/

        Mat dstWarped = dst.clone();
        /*dstWarped.convertTo(dstWarped, CV_32F);
        src.convertTo(src, CV_32F);*/
        for (int i = 0; i < srcTriangleList.size(); i++) {
            FloatPointer srcPointer = srcTriangleList.get(i);
            //FloatPointer dstPointer = dstTriangleList.get(i);

            Mat mask = new Mat(src.size(), CV_8U);
            Point p = new Point();
            Point p1 = new Point(cvRound(srcPointer.get(0)), cvRound(srcPointer.get(1)));
            Point p2 = new Point(cvRound(srcPointer.get(2)), cvRound(srcPointer.get(3)));
            Point p3 = new Point(cvRound(srcPointer.get(4)), cvRound(srcPointer.get(5)));
            p.position(0).put(p1);
            p.position(1).put(p2);
            p.position(2).put(p3);
            floodFill(mask, p, new Scalar().green(225).blue(225).red(225));
            imshow("ces", mask);
            waitKey(0);

            /*
            Mat mask = Mat.zeros(src.rows(), src.cols(), src.type()).asMat();
            Mat affineTransform = getAffineTransform(change(srcPointer), change(dstPointer));
            drawContours(mask, changeToMatVector(srcPointer), 0, new Scalar());
            src.copyTo(mask);
            warpAffine(src, dstWarped, affineTransform, dstWarped.size());
            */
        }
        MatExpr zeros = Mat.zeros(dstWarped.rows(), dstWarped.cols(), dstWarped.depth());
        //Point point = changeVec6fVectorToPiint(dstTriangleList);
        //fillConvexPoly(zeros.asMat(), point, 1, new Scalar().blue(255).red(255).green(255));
        boundingRect(dstWarped);
        imshow("ceshi", dstWarped);
        waitKey(0);

        //Rect srcRect = boundingRect(src);
        // Rect dstWarpedRect = boundingRect(dstWarped);
        /*for (int j = 0; j < srcVector.get(0).size(); j++) {
            for (int i = 0; i < vec6fVector.size(); i++) {
                System.out.println(vec6fVector.get(i).get());
                fillConvexPoly();
                MatExpr expr = Mat.zeros(dstWarpedRect.height(), dstWarpedRect.width(), CV_32FC3);
            }
        }*/


        /*Mat dstWarped = dst.clone();
        dstWarped.convertTo(dstWarped, CV_32F);
        src.apply(rect);
        Mat srcCopy = src.clone();


        imshow("ceshi", src);
        waitKey(0);
        Mat transform = getAffineTransform(src, mask.asMat());
        warpAffine(src, mask.asMat(), transform, mask.asMat().size());
        multiply(src, mask.asMat(), src);
        Mat dst = new Mat();
        subtract(mask.asMat(), dst);
        multiply(src, dst);
        absdiff(dst, mask.asMat(), dst);
        imshow("ceshi", src);
        waitKey(0);*/
    }

    private static Point changeVec6fVectorToPiint(Vec6fVector dstTriangleList) {
        Point point = new Point();
        for (int i = 0; i < dstTriangleList.size(); i++) {
            point.put(dstTriangleList.get(i));
        }
        return point;
    }

    public static void compositeVideo(String saveMp4name, String imagesPath) throws Exception {
        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(saveMp4name, 640, 480);
        //recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // 28
        //recorder.setVideoCodec(avcodec.AV_CODEC_ID_FLV1); // 28
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_MPEG4); // 13
        recorder.setFormat("mp4");
        //	recorder.setFormat("mov,mp4,m4a,3gp,3g2,mj2,h264,ogg,MPEG4");
        recorder.setFrameRate(20);
        recorder.setPixelFormat(0); // yuv420p
        recorder.start();
        //
        OpenCVFrameConverter.ToIplImage conveter = new OpenCVFrameConverter.ToIplImage();
        // 列出目录中所有的图片，都是jpg的，以1.jpg,2.jpg的方式，方便操作
        File src = new File(imagesPath);
        File[] files = src.listFiles();
        // 循环所有图片
        assert files != null;
        for (File file : files) {
            IplImage image = opencv_imgcodecs.cvLoadImage(file.getCanonicalPath()); // 非常吃内存！！
            recorder.record(conveter.convert(image));
            cvReleaseImage(image);
        }
        recorder.stop();
        recorder.release();
    }

    public static void extractAudio() throws Exception {
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber("E:\\0.mp4");
        grabber.setFormat("mp4");
        grabber.start();
        Frame audioSamples;
        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder("E:\\1.aac", 2);
        recorder.setAudioOption("crf", "0");
        recorder.setAudioQuality(0);
        recorder.setAudioBitrate(grabber.getAudioBitrate());
        recorder.setAudioChannels(grabber.getAudioChannels());
        recorder.setAudioCodec(grabber.getAudioCodec());
        recorder.start();
        while ((audioSamples = grabber.grabSamples()) != null) {
            recorder.setTimestamp(grabber.getTimestamp());
            recorder.record(audioSamples);
        }
        recorder.stop();
        recorder.release();
    }

    public static void extractImage() throws Exception {
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber("E:\\0.mp4");
        grabber.setFormat("mp4");
        grabber.start();
        int len = grabber.getLengthInFrames();
        Java2DFrameConverter converter = new Java2DFrameConverter();
        for (int i = 1; i <= len; i++) {
            Frame frame = grabber.grabFrame();
            BufferedImage image = converter.convert(frame);
            if (image != null) {
                ImageIO.write(image, "jpg", new File("E:\\test\\" + String.format("%010d", i) + ".jpg"));
            }
        }
    }
}
